#include "rna/network.h"

Network::Network(const std::vector<size_t> &neuronsCount, Activation *activation) {
    size_t neurons, previous;

    _layersCount = neuronsCount.size();
    _synapses = new data_t** [this->_layersCount];
    _neuronsCount = new size_t [this->_layersCount];
    _layerActivation = new Activation* [this->_layersCount];
    _biases = new data_t* [this->_layersCount];
    _neuronsCount[0] = previous = neuronsCount[0];

    for ( size_t l = 1; l < this->_layersCount; l++ ) {
        neurons = neuronsCount[l];
        _neuronsCount[l] = neurons;
        _synapses[l] = new data_t* [neurons];
        _layerActivation[l] = activation;
        _biases[l] = new data_t [neurons];
        for ( size_t n = 0; n < neurons; n++ ) {
            _synapses[l][n] = new data_t [previous];
        }
        previous = neurons;
    }
}

data_t* Network::feed(data_t *input) const {
    size_t layersCount = _layersCount,
            inputCount = _neuronsCount[0],
            count;

    data_t *_input = new data_t[inputCount],
            accumulator, *output = nullptr;

    std::copy(input, input+inputCount, _input);

    for ( size_t l = 1; l < layersCount; l++ ) {
        count = _neuronsCount[l];
        output = new data_t[count];

        for ( size_t n = 0; n < count; n++ ) {
            accumulator = _biases[l][n];
            for ( size_t s = 0; s < inputCount; s++ ) {
                accumulator += _synapses[l][n][s] * _input[s];
            }
            output[n] = _layerActivation[l]->function(accumulator);
        }

        inputCount = count;
        delete _input;
        _input = output;
    }

//    ArrayUtils::print(input, _neuronsCount[0]);
//    std::cout << "\t ";
//    ArrayUtils::print(output, count);
//    std::cout << std::endl;

    return output;
}

void Network::applyChages(data_t ***weightChanges, data_t **biasChanges) {
    size_t input = _neuronsCount[0],
            neurons;

    for ( size_t l = 1; l < _layersCount; l++ ) {
        neurons = _neuronsCount[l];
        for ( size_t n = 0; n < neurons; n++ ) {
            for ( size_t s = 0; s < input; s++ ) {
                _synapses[l][n][s] += weightChanges[l][n][s];
            }
            _biases[l][n] += biasChanges[l][n];
        }
        input = neurons;
    }
}

Network::~Network() {
    for ( size_t l = 1; l < _layersCount; l++ ) {
        for ( size_t n = 0; n < _neuronsCount[0]; n++ ) {
            delete _synapses[l][n];
        }
        delete _synapses[l];
        delete _biases[l];
    }
    delete _synapses;
    delete _biases;
    delete _neuronsCount;
}

Activation* Network::activation(size_t layer) const {
    return _layerActivation[layer];
}

data_t*** Network::synapses() {
    return _synapses;
}

data_t** Network::biases() {
    return _biases;
}

size_t Network::layersCount() const {
    return _layersCount;
}

size_t Network::neuronsCount(size_t layer) const {
    return _neuronsCount[layer];
}

void Network::print() {
    size_t input = _neuronsCount[0],
            neurons;

    std::cout << "\nNetwork::print " << std::endl;
    for ( size_t l = 1; l < _layersCount; l++ ) {
        neurons = _neuronsCount[l];
        std::cout << "*";
        for ( size_t n = 0; n < neurons; n++ ) {
            for ( size_t s = 0; s < input; s++ ) {
                std::cout << " " << _synapses[l][n][s];
            }
            std::cout << " " << _biases[l][n] << " |";
        }
        std::cout << "\b \n";
        input = neurons;
    }
}
